package com.wl.spring;

import com.wl.spring.biz.UserBiz;
import lombok.Data;
import org.springframework.context.annotation.*;
import org.springframework.stereotype.Component;
import org.springframework.stereotype.Controller;
import org.springframework.stereotype.Repository;
import org.springframework.stereotype.Service;

import java.io.File;
import java.lang.annotation.Annotation;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.net.URL;
import java.net.URLDecoder;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

public class MyAnnotationConfigApplicationContext {    Class configClass;
    Object configObject;

    Map<String,BeanDefinition> beanDefinitionMap = new HashMap<>();
    Map<String,Object> singtonObjects = new HashMap<>();

    public MyAnnotationConfigApplicationContext(Class configClass) throws Exception {
        this.configClass = configClass;
        configObject = configClass.newInstance();

        // 读取配置(基于注解) 创建 BeanBefinition 对象, 存入 BeanBefinitionMap 容器
        addBeanDefinitionFromMethod();
        addBeanDefinitionFromPackage();
        beanDefinitionMap.forEach((id,bean)->{
            System.out.printf("%s: %s \n", id, bean);
        });


        // 根据 BeanDefinition 创建 Bean, 存入 IOC singtonObjects
        createBean();

        // 后续实现 getBean

        // TODO 实现自动装载 autowired

    }

    public Object getBean(String id){
        Object bean = singtonObjects.get(id);
        if(bean == null){
            throw new RuntimeException("没有该bean: " + id);
        }
        return bean;
    }

    public <T> T getBean(Class<T> tClass){
        // stream 编程
        List<Map.Entry> list = singtonObjects.entrySet()
                .stream()
                .filter(entry -> entry.getValue().getClass().isAssignableFrom(tClass))
                .collect(Collectors.toList());
        if(list.isEmpty()){
            throw new RuntimeException("没有该bean: " + tClass);
        } else if(list.size() == 1){
            return (T) list.get(0).getValue();
        } else {
            List<Map.Entry> list1 = list.stream()
                    .filter(entry -> {
                        BeanDefinition bd = beanDefinitionMap.get(entry.getKey());
                        return bd.isPrimary();
                    })
                    .collect(Collectors.toList());
            if(list1.isEmpty()){
                throw new RuntimeException("该bean没有定义primary bean: " + tClass);
            } else if (list1.size() == 1){
                return (T) list1.get(0).getValue();
            } else {
                throw new RuntimeException("该bean定义了多个主primary bean: " + tClass);
            }
        }

    }

    private void createBean() throws Exception {
        for (Map.Entry<String, BeanDefinition> entry : beanDefinitionMap.entrySet()) {
            String id = entry.getKey();
            BeanDefinition bd = entry.getValue();
            if(bd.beanMethod != null){
                // 仅执行五参数的 bean 方法
                if(bd.beanMethod.getParameterCount() == 0){
                    bd.beanMethod.setAccessible(true);
                    Object bean = bd.beanMethod.invoke(configObject);
                    singtonObjects.put(id, bean);
                }
            } else {
                Object bean = bd.beanClass.newInstance();
                singtonObjects.put(id, bean);
            }
        }
    }

    private void addBeanDefinitionFromPackage() throws Exception {
        ComponentScan componentScan = (ComponentScan) configClass.getAnnotation(ComponentScan.class);
        if(componentScan != null){
            String[] packages = componentScan.value();
            URL url = configClass.getClassLoader().getResource("");
            // 编译目录路径
            String classesPath = URLDecoder.decode( url.getPath()).substring(1);
            // 扫描的根路径
            String scanRootPath;
            List<File> classFileList = new ArrayList<>();
            File classesFile = new File(classesPath);
            for (String aPackage : packages) {
                System.out.println("aPackage = " + aPackage);
                scanRootPath = classesPath + aPackage.replaceAll("\\.", "/");
                scanClassFile(new File(scanRootPath), classFileList);
            }
            for (File classFile : classFileList) {
                String classPath = classFile.getAbsolutePath()
                        .replace(classesFile.getAbsolutePath(), "");
                classPath = classPath.replace(".class","");
                classPath = classPath.substring(1);
                classPath = classPath.replaceAll("\\\\",".");
                System.out.println("classPath = " + classPath);
                Class beanClass = Class.forName(classPath);

                Service service = (Service) beanClass.getAnnotation(Service.class);
                Repository repository = (Repository) beanClass.getAnnotation(Repository.class);
                Controller controller = (Controller) beanClass.getAnnotation(Controller.class);
                Component component = (Component) beanClass.getAnnotation(Component.class);

                if(service == null &&
                        repository == null &&
                        controller == null &&
                        component == null){
                    continue;
                }

                // 构建 BeanDefinition
                BeanDefinition bd = new BeanDefinition();
                bd.beanClass = beanClass;
                bd.lazy = beanClass.getAnnotation(Lazy.class) != null;
                bd.primary = beanClass.getAnnotation(Primary.class) != null;
                if(beanClass.getAnnotation(Scope.class) != null){
                    bd.scope = ((Scope)beanClass.getAnnotation(Scope.class)).value();
                }
                if(service!=null && service.value().isBlank() == false){
                    bd.id = service.value();
                } else if(repository!=null && repository.value().isBlank() == false){
                    bd.id = repository.value();
                } else if(controller!=null && controller.value().isBlank() == false){
                    bd.id = controller.value();
                } else if(component!=null && component.value().isBlank() == false){
                    bd.id = component.value();
                } else {
                    String id = beanClass.getSimpleName();
                    // 类名首字母小写
                    id = id.substring(0,1).toLowerCase() + id.substring(1);
                    bd.id = id;
                }
                beanDefinitionMap.put(bd.id, bd);
            }
        }
    }
    private void scanClassFile(File dir, List<File> classFileList){
        File[] classFiles = dir.listFiles(file -> file.getName().endsWith(".class"));
        for (File classFile : classFiles) {
            classFileList.add(classFile);
        }
        File[] subDirs = dir.listFiles(file -> file.isDirectory());
        for (File subDir : subDirs) {
            scanClassFile(subDir, classFileList);
        }
    }


    private void addBeanDefinitionFromMethod() {
        Method[] declaredMethods = configClass.getDeclaredMethods();
        for (Method method : declaredMethods) {
            // 判断是否是Bean方法
            if (method.getAnnotation(Bean.class)!=null) {
                // Bean 方法
                BeanDefinition bd = new BeanDefinition();
                bd.beanMethod = method;
                bd.beanClass = method.getReturnType();
                bd.lazy = method.getAnnotation(Lazy.class) != null;
                bd.primary = method.getAnnotation(Primary.class) != null;
                Bean beanAnno = method.getAnnotation(Bean.class);
                if(beanAnno.value().length>0){
                    bd.id = beanAnno.value()[0];
                } else {
                    bd.id = method.getName();
                }
                if(method.getAnnotation(Scope.class) != null){
                    bd.scope = method.getAnnotation(Scope.class).value();
                }
                beanDefinitionMap.put(bd.id, bd);
            }
        }
    }

    public static void main(String[] args) throws Exception {
        MyAnnotationConfigApplicationContext cxt;
        cxt = new MyAnnotationConfigApplicationContext(
                AutowireAnnoConfig.class);
        System.out.println("-----------------------------------------");
        Object userMapper = cxt.getBean("userMapper");
        System.out.println("userMapper = " + userMapper);

        UserBiz userBiz = cxt.getBean(UserBiz.class);
        System.out.println("userBiz = " + userBiz);

        cxt.getBean("userMappersss");
    }
}

@Data
class BeanDefinition{
    String id;
    Class beanClass;
    Method beanMethod;
    boolean lazy;
    String scope;
    boolean primary;
    // TODO 扩展其他属性
}
